package lb.io.lblet;

import lb.io.utils.Klog;
import lb.io.utils.Strings;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class LBKit {
    private static final Logger klog = Klog.getLogger(LBKit.class);

    public static class Sqlplus {

        private String LB_ROOT;

        private String LB_ORACLE_USER;

        private String LB_ORACLE_HOME;

        private String LB_ORACLE_TNSDIR;

        private String op;

        private String sid;

        private List<String> params;

        private Sqlplus() {
        }

        public static Sqlplus SqlplusBuilder(String root) {
            Sqlplus sqlplus = new Sqlplus();
            sqlplus.LB_ROOT = root;
            return sqlplus;
        }

        public Sqlplus Op(String op) {
            this.op = op;
            return this;
        }

        public Sqlplus Sid(String sid) {
            this.sid = sid;
            return this;
        }

        public Sqlplus User(String user) {
            this.LB_ORACLE_USER = user;
            return this;
        }

        public Sqlplus Home(String home) {
            this.LB_ORACLE_HOME = home;
            return this;
        }

        public Sqlplus Listen(String listen) {
            this.LB_ORACLE_TNSDIR = listen;
            return this;
        }

        public Sqlplus Parameters(List<String> params) {
            this.params = params;
            return this;
        }

        private List<String> Scripts() {
            List<String> scripts = new LinkedList<>();
            scripts.add(Paths.get(LB_ROOT, "oracle", "sqlplusShell.sh").toString());
            String query = "";
            switch (op) {
                case "getDBName":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getDBName.sql").toString();
                    break;
                case "getDBStatus":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getDBStatus.sql").toString();
                    break;
                case "getDBRole":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getDBRole.sql").toString();
                    break;
                case "switchArchiveLog":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "switchArchiveLog.sql").toString();
                    break;
                case "getOpenMode":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getOpenMode.sql").toString();
                    break;
                case "getDBLogMode":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getDBLogMode.sql").toString();
                    break;
                case "getDBFreeSize":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getDBFreeSize.sql").toString();
                    break;
                case "getDBSize":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getDBMaxSize.sql").toString();
                    break;
                case "getDBVersion":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getDBVersion.sql").toString();
                    break;
                case "getRmanJobDetails":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getRmanBackupJobDetails.sql").toString();
                    break;
                case "getRmanTime":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getRmanTime.sql").toString();
                    break;
                case "getRmanLogTime":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getRmanLogTime.sql").toString();
                    break;
                case "getInstanceStatus":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getInstanceStatus.sql").toString();
                    break;
                case "getBackupProcess":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getBackupProcess.sql").toString();
                    break;
                case "getBackupStatus":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getBackupStatus.sql").toString();
                    break;
                case "getBCTStatus":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getBCTStatus.sql").toString();
                    break;
                case "getArchiveDest":
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getArchiveDest.sql").toString();
                    break;
                default:
                    query = Paths.get(LB_ROOT, "oracle", "sqlplusShell", "getDBStatus.sql").toString();
                    break;
            }
            scripts.add(query);
            scripts.add(sid);
            if (params != null && params.size() != 0) {
                scripts.add(Strings.Join(params, ','));
            }
            return scripts;
        }

        public String Commit() {
            return Strings.Join(Scripts(), ' ');
        }

        public String Execute() throws RuntimeException {
            if (!this.LB_ROOT.equals("")) {
                LBKit.export("LB_ROOT", LB_ROOT);
            }
            if (!this.LB_ORACLE_USER.equals("")) {
                LBKit.export("LB_ORACLE_USER", LB_ORACLE_USER);
            }
            if (!this.LB_ORACLE_HOME.equals("")) {
                LBKit.export("LB_ORACLE_HOME", LB_ORACLE_HOME);
            }
            if (!this.LB_ORACLE_TNSDIR.equals("")) {
                LBKit.export("LB_ORACLE_TNSDIR", LB_ORACLE_TNSDIR);
            }
            return LBKit.execute(Commit());
        }
    }

    public static class Get {
        private Get() {
        }

        public static String OracleASM() {
            return LBKit.execute("ps aux | grep asm_pmon_| grep -v grep | cut -d\"_\" -f3");
        }

        public static List<String> OracleSid() {
            String shell = "ps aux | grep ora_pmon | grep -v grep | cut -d\"_\" -f3";
            String out = LBKit.execute(shell);
            List<String> dbs = new LinkedList<>();
            for (String item : out.split("\n")) {
                item = item.trim();
                if (item.equals("")) {
                    continue;
                }
                dbs.add(item);
            }
            return dbs;
        }

        public static String TNSAdmin(String user) {
            String shell = "";
            if (user.equals("")) {
                shell = "echo $TNS_ADMIN";
            } else {
                shell = "su - " + user + " -c \"echo \\$TNS_ADMIN\"";
            }
            return LBKit.execute(shell);
        }

        public static String OracleHome(String sid) {
            if (sid.equals("")) {
                return LBKit.execute("su - oracle -c \"echo \\$ORACLE_HOME\"");
            }
            return LBKit.execute(String.format("cat /etc/oratab | grep %s | cut -d\":\" -f2", sid));
        }

        public static String OracleUser(String sid) {
            if (sid.equals("")) {
                return "oracle";
            }
            return LBKit.execute(String.format("ps aux | grep ora_pmon_%s | grep -v grep | cut -d\" \" -f1 ", sid));
        }

        public static String ASMUser(String sid) {
            if (sid.equals("")) {
                return "grid";
            }
            return LBKit.execute(String.format("ps aux | grep asm_pmon_%s | grep -v grep | cut -d\" \" -f1", sid));
        }
    }

    public static class iscsi {
        private String root;

        private String iqn;

        private String ip;

        public iscsi(String root, String ip, String iqn) {
            this.root = root;
            this.ip = ip;
            this.iqn = iqn;
        }

        public String start() {
            String out = LBKit.execute(String.format("%s/disk/iscsi.sh start %s %s", root, ip, iqn));
            if (out.startsWith("---:")) {
                return Strings.TrimPrefix(out, "---:");
            }
            return "";
        }

        public String stop() {
            return LBKit.execute(String.format("%s/disk/iscsi.sh stop %s %s", root, ip, iqn));
        }
    }

    public static class fsDisk {
        private String root;

        private String point;

        private String source;

        public fsDisk(String root, String point, String source) {
            this.root = root;
            this.point = point;
            this.source = source;
        }

        public String create() throws RuntimeException {
            return LBKit.execute(String.format("%s/disk/fsdisk.sh create %s %s", root, point, source));
        }

        public String rename() throws RuntimeException {
            return LBKit.execute(String.format("%s/disk/fsdisk.sh rename %s %s", root, point, source));
        }

        public String connect() throws RuntimeException {
            return LBKit.execute(String.format("%s/disk/fsdisk.sh connect %s %s", root, point, source));
        }

        public String disconnect() throws RuntimeException {
            return LBKit.execute(String.format("%s/disk/fsdisk.sh disconnect %s %s", root, point, source));
        }

        public String delete() throws RuntimeException {
            return LBKit.execute(String.format("%s/disk/fsdisk.sh delete %s %s", root, point, source));
        }
    }

    public static class rawDisk {
        private String root;

        private String source;

        private String name;

        private String user;

        public rawDisk(String root, String name, String source, String user) {
            this.root = root;
            this.name = name;
            this.source = source;
            this.user = user;
        }

        public String create() throws RuntimeException {
            return LBKit.execute(String.format("%s/disk/rawdisk.sh create %s %s %s", root, name, source, user));
        }

        public String delete() throws RuntimeException {
            return LBKit.execute(String.format("%s/disk/rawdisk.sh delete %s %s %s", root, name, source, user));
        }
    }

    public static class diskgroup {
        private String root;

        private String name;

        private String sid;

        private String LB_ORACLE_USER;

        private String LB_ORACLE_HOME;

        private String LB_ORACLE_TNSDIR;

        private List<String> disks = new LinkedList<>();

        public diskgroup(String root, String name, String sid, List<String> disks) {
            this.root = root;
            this.name = name;
            this.sid = sid;
            this.disks = disks;
        }

        public diskgroup User(String user) {
            this.LB_ORACLE_USER = user;
            return this;
        }

        public diskgroup Home(String home) {
            this.LB_ORACLE_HOME = home;
            return this;
        }

        public diskgroup Listen(String listen) {
            this.LB_ORACLE_TNSDIR = listen;
            return this;
        }

        private void prepare() {
            if (!this.LB_ORACLE_USER.equals("")) {
                LBKit.export("LB_ORACLE_USER", LB_ORACLE_USER);
            }
            if (!this.LB_ORACLE_HOME.equals("")) {
                LBKit.export("LB_ORACLE_HOME", LB_ORACLE_HOME);
            }
            if (!this.LB_ORACLE_TNSDIR.equals("")) {
                LBKit.export("LB_ORACLE_TNSDIR", LB_ORACLE_TNSDIR);
            }
        }

        public String create() throws RuntimeException {
            prepare();
            return LBKit.execute(String.format("%s/oracle/diskGroup.sh create %s %s %s",
                    root, name, sid, Strings.Join(disks, ',')));
        }

        public String connect() throws RuntimeException {
            prepare();
            return LBKit.execute(String.format("%s/oracle/diskGroup.sh connect %s", root, name));
        }

        public String disconnect() throws RuntimeException {
            prepare();
            return LBKit.execute(String.format("%s/oracle/diskGroup.sh disconnect %s", root, name));
        }

        public String rename(String newName) throws RuntimeException {
            prepare();
            return LBKit.execute(String.format("%s/oracle/diskGroup.sh rename %s %s %s %s",
                    root, name, sid, Strings.Join(disks, ','), newName));
        }

        public String delete(boolean force) throws RuntimeException {
            return LBKit.execute(String.format("%s/oracle/diskGroup.sh delete %s %s %s",
                    root, name, sid, force ? "force" : ""));
        }
    }

    public static class Rman {

        private String LB_ROOT;

        private String LB_ORACLE_USER;

        private String LB_ORACLE_HOME;

        private String LB_ORACLE_TNSDIR;

        private String op;

        private String db;

        private String sid;

        private String target;

        private String store;

        private String rlog;

        private String fromTime;

        private String untilTime;

        boolean asm;

        private Rman() {
        }

        public static Rman RmanBuilder(String root) {
            Rman rman = new Rman();
            rman.LB_ROOT = root;
            return rman;
        }

        public Rman Op(String op) {
            this.op = op;
            return this;
        }

        public Rman DB(String db) {
            this.db = db;
            return this;
        }

        public Rman Sid(String sid) {
            this.sid = sid;
            return this;
        }

        public Rman Target(String target) {
            this.target = target;
            return this;
        }

        public Rman Store(String store) {
            this.store = store;
            return this;
        }

        public Rman Rlog(String rlog) {
            this.rlog = rlog;
            return this;
        }

        public Rman fromTime(String fromTime) {
            this.fromTime = fromTime;
            return this;
        }

        public Rman utilTime(String untilTime) {
            this.untilTime = untilTime;
            return this;
        }

        public Rman EnableASM() {
            this.asm = true;
            return this;
        }

        public Rman User(String user) {
            this.LB_ORACLE_USER = user;
            return this;
        }

        public Rman Home(String home) {
            this.LB_ORACLE_HOME = home;
            return this;
        }

        public Rman Listen(String listen) {
            this.LB_ORACLE_TNSDIR = listen;
            return this;
        }

        private List<String> Scripts() throws RuntimeException {
            List<String> scripts = new LinkedList<>();
            scripts.add(Paths.get(LB_ROOT, "oracle", "rmanShell.sh").toString());
            String rcv = "";
            switch (op) {
                case "check":
                    rcv = asm ? Paths.get(LB_ROOT, "oracle", "rmanShell", "asmcheckBackup.rcv").toString()
                            : Paths.get(LB_ROOT, "oracle", "rmanShell", "fscheckBackup.rcv").toString();
                    break;
                case "backup":
                    rcv = asm ? Paths.get(LB_ROOT, "oracle", "rmanShell", "asmbackupDatabase.rcv").toString()
                            : Paths.get(LB_ROOT, "oracle", "rmanShell", "fsbackupDatabase.rcv").toString();
                    break;
                case "log":
                    rcv = asm ? Paths.get(LB_ROOT, "oracle", "rmanShell", "asmbackupLog.rcv").toString()
                            : Paths.get(LB_ROOT, "oracle", "rmanShell", "fsbackupLog.rcv").toString();
                    break;
            }
            File file = new File(rcv);
            if (!file.exists()) {
                throw new RuntimeException(String.format("%s 不存在", rcv));
            }
            StringBuilder sb = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));) {
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            } catch (IOException e) {
                klog.error(e.getMessage());
                throw new RuntimeException(e.getMessage());
            }
            String rcvData = sb.toString();
            rcvData = rcvData.replaceAll("LINKBACKUP", target);
            rcvData = rcvData.replaceAll("RLOG", rlog);
            rcvData = rcvData.replaceAll("LB", db);
            rcvData = rcvData.replaceAll("STORES", store);
            if (fromTime != null && !fromTime.equals("")) {
                rcvData = rcvData.replaceAll("FROM", fromTime);
            }
            if (untilTime != null && !untilTime.equals("")) {
                rcvData = rcvData.replaceAll("UTIL", untilTime);
            }
            String tempQuery = Paths.get(store, db + "-" + sid + ".rcv").toString();
            file = new File(tempQuery);
            if (!file.exists()) {
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    klog.error(e.getMessage());
                    e.printStackTrace();
                    throw new RuntimeException(e.getMessage());
                }
            }
            try (Writer writer = new FileWriter(file)) {
                writer.write(rcvData);
            } catch (IOException e) {
                klog.error(e.getMessage());
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
            }
            scripts.add(tempQuery);
            scripts.add(db);
            scripts.add(sid);
            scripts.add(target);
            scripts.add(store);
            scripts.add(rlog);
            return scripts;
        }

        public String Commit() throws RuntimeException {
            return Strings.Join(Scripts(), ' ');
        }

        public String Execute() throws RuntimeException {
            if (!this.LB_ROOT.equals("")) {
                LBKit.export("LB_ROOT", LB_ROOT);
            }
            if (!this.LB_ORACLE_USER.equals("")) {
                LBKit.export("LB_ORACLE_USER", LB_ORACLE_USER);
            }
            if (!this.LB_ORACLE_HOME.equals("")) {
                LBKit.export("LB_ORACLE_HOME", LB_ORACLE_HOME);
            }
            if (!this.LB_ORACLE_TNSDIR.equals("")) {
                LBKit.export("LB_ORACLE_TNSDIR", LB_ORACLE_TNSDIR);
            }
            return LBKit.execute(Commit());
        }
    }

    public static class Recover {

        private String LB_ROOT;

        private String LB_ORACLE_USER;

        private String LB_ORACLE_HOME;

        private String LB_ORACLE_TNSDIR;

        private String op;

        private String osid;

        private String sid;

        private String target;

        private String store;

        private String rlog;

        private String params;

        private String timestamp;

        boolean asm = false;

        private Recover() {
        }

        public static Recover RecoverBuilder(String root) {
            Recover recover = new Recover();
            recover.LB_ROOT = root;
            return recover;
        }

        public Recover Op(String op) {
            this.op = op;
            return this;
        }

        public Recover Osid(String osid) {
            this.osid = osid;
            return this;
        }

        public Recover Sid(String sid) {
            this.sid = sid;
            return this;
        }

        public Recover Target(String target) {
            this.target = target;
            return this;
        }

        public Recover Store(String store) {
            this.store = store;
            return this;
        }

        public Recover Rlog(String rlog) {
            this.rlog = rlog;
            return this;
        }

        public Recover Params(String params) {
            this.params = params;
            return this;
        }

        public Recover Timestamp(String timestamp) {
            this.timestamp = timestamp;
            return this;
        }

        public Recover EnableASM() {
            this.asm = true;
            return this;
        }

        public Recover User(String user) {
            this.LB_ORACLE_USER = user;
            return this;
        }

        public Recover Home(String home) {
            this.LB_ORACLE_HOME = home;
            return this;
        }

        public Recover Listen(String listen) {
            this.LB_ORACLE_TNSDIR = listen;
            return this;
        }

        private List<String> Scripts() throws RuntimeException {
            List<String> scripts = new ArrayList<>();
            String recoverSh = asm ? Paths.get(LB_ROOT, "oracle", "asmRecover.sh").toString()
                    : Paths.get(LB_ROOT, "oracle", "fsRecover.sh").toString();
            scripts.add(recoverSh);
            switch (op) {
                case "create":
                    scripts.add("create");
                    scripts.add(osid);
                    scripts.add(sid);
                    scripts.add(target);
                    scripts.add(store);
                    scripts.add(timestamp);
                    scripts.add(rlog);
                    scripts.add(params);
                    break;
                case "delete":
                    scripts.add("delete");
                    scripts.add(sid);
                    scripts.add(target);
                    scripts.add(store);
                    scripts.add(rlog);
                    break;
            }
            return scripts;
        }

        public String Commit() throws RuntimeException {
            return Strings.Join(Scripts(), ' ');
        }

        public String Execute() throws RuntimeException {
            if (!this.LB_ROOT.equals("")) {
                LBKit.export("LB_ROOT", LB_ROOT);
            }
            if (!this.LB_ORACLE_USER.equals("")) {
                LBKit.export("LB_ORACLE_USER", LB_ORACLE_USER);
            }
            if (!this.LB_ORACLE_HOME.equals("")) {
                LBKit.export("LB_ORACLE_HOME", LB_ORACLE_HOME);
            }
            if (!this.LB_ORACLE_TNSDIR.equals("")) {
                LBKit.export("LB_ORACLE_TNSDIR", LB_ORACLE_TNSDIR);
            }
            return LBKit.execute(Commit());
        }
    }

    public static String execute(String shell) throws RuntimeException {
        klog.info(shell);
        String result = "";
        List<String> cmds = new LinkedList<>();
        cmds.add("sh");
        cmds.add("-c");
        cmds.add(shell);
        ProcessBuilder pb = new ProcessBuilder(cmds);
        pb.redirectErrorStream(true);
        try {
            Process process = pb.start();
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            StringBuffer sb = new StringBuffer();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append('\n');
            }
            /* 用shell返回值命令执行是否成功 */
            int exitValue = process.waitFor();
            if (exitValue == 0) {
                result = sb.toString();
            } else {
                String message = String.format("%s exit %d: %s", shell, exitValue, sb.toString());
                throw new RuntimeException(message);
            }
        } catch (InterruptedException | IOException e) {
            throw new RuntimeException(e.getMessage());
        }

        if (result.endsWith("\n")) {
            result = Strings.TrimSuffix(result, "\n");
        }
        return result.trim();
    }

    public static void export(String k, String v) throws RuntimeException {
        String shell = String.format("export %s=%s", k, v);
        List<String> cmds = new LinkedList<>();
        cmds.add("sh");
        cmds.add("-c");
        cmds.add(shell);
        ProcessBuilder pb = new ProcessBuilder(cmds);
        try {
            pb.start().waitFor();
        } catch (InterruptedException | IOException e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}
